HTTP, Web, and JSON
CIS 193 – Go Programming
Prakhar Bhandari, Adel Qalieh
CIS 193
Prakhar Bhandari, Adel Qalieh
CIS 193
HTTP (Hyper Text Transfer Protocol) is a client-server protocol. Remember that a server is an application that listens for incoming requests from clients, and returns and appropriate response.
When you access a page on the web, you make an HTTP request to the webserver hosting the page, and you get the HTML from the server as a response.
HTTP has verbs such as GET, POST, DELETE, etc.
Making HTTP requests as a client in Go is simple. The http
package is full of methods for making HTTP requests.
resp, err := http.Get("http://example.com/") ... resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) ... resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
Variable resp
is of type *Response
, which has a Status
, Header
, Body
, etc.
resp.Status // "200 OK" resp.StatusCode // 200 resp.Proto // "HTTP/1.0"
The response body (resp.Body
) is an io.ReadCloser
. To read the body, you must treat it like any other type with a Reader, like files.
type ReadCloser interface { Reader Closer }
After you are done reading the Body
, you must Close()
it. Why?
Making a barebones HTTP server in Go is just as easy. The http
package also comes with a fully-featured HTTP server.
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) }) log.Fatal(http.ListenAndServe(":8080", nil))
ResponseWriter
has a Write([]byte)
method. What interface does it fulfill automatically?
The web is made up of HTML documents that are served over HTTP. We can use HTML responses and do HTML templating with the built-in template
package.
var homeTemplate = template.Must(template.ParseFiles("home.html")) func upload(w http.ResponseWriter, r *http.Request) { homeTemplate.Execute(w, nil) }
Example:
{ "id": 1, "name": "A green door", "price": 12.50, "tags": ["home", "green"] }
The equivalent native type in Go would be:
type Door struct { ID int Name string Price float64 Tags []string }
The goal is to convert this
{ "name":"Gopher", "birthdate": "2017/02/28", "shirt-size": "XS" }
into this
type Person struct { Name string Born time.Time Size string }
{ "id": 1, "name": "A green door", "price": 12.50, "tags": ["home", "green"] }
Step 1: Add struct tags
type Door struct { ID int `json:"id"` Name string `json:"name"` Price float64 `json:"price"` Tags []string `json:"tags"` }
This sets the key used for encoding and decoding JSON. If the tag is omitted, the default is the struct field name. (ex: "Price")
Use the Marshal
and Unmarshal
methods from the json
package to convert back and forth between JSON as an array of bytes and its struct representation.
Encoding aka Marshaling
ourDoor := &Door{ ID: 1, Name: "A gray door", Price: 24.99, Tags: []string{"old", "engineering"}, } jsonDoor, err := json.Marshal(ourDoor)
Decoding aka Unmarshaling
incomingDoor := []byte(`{"id": 2, "name": "red door", "price": 99.50, "tags": []}`) newDoor := Door{} err := json.Unmarshal(incomingDoor, &newDoor)
Ideally, JSON should be structured enough that type checking will help you. However, this is not always this case.
{ "doors": [ { "id": 1, "name": "A green door" }, { "id": "2", "name": ["white and gold door", "blue and black door"] } ] }
Whenever your data type is unknown, use an interface and runtime type conversions.
var data map[string]interface{} err := json.Unmarshal(incomingDoor, &data)
Remember: interface{}
requires type casting. See type switching for a more sophisticated case of type conversions.
id := data["id"] // interface{} (wrong!) id++ // invalid operation: id++ (non-numeric type interface {}) id := data["id"].(int) // int (correct!) id++ // => 3
json.Marshal
only knows how to convert some basic native types. What about our time.Time type in the original example?
type Person struct { Name string Born time.Time // "2017/02/28" Size string }
Solution: Use a map[string]string
, and convert types.
See the json
documentation for more details.
Time formatting is based on a "magic" date:
Mon Jan 2 15:04:05 -0700 MST 2006 0 1 2 3 4 5 7 6
Simply order the "magic" date into the format that you want!
now := time.Now() fmt.Println(now) => 2017-02-28 10:44:46.584220595 -0500 EST fmt.Println(now.Format("1/2/06")) => 2/28/17 fmt.Println(now.Format("2006-01-02")) => 2007-02-28
You can have arbitrary text in your date format strings as long as they don't conflict with the magic date keywords.
fmt.Println(now.Format("Today is Monday, January 2nd!")) fmt.Println(now.Format("Alert: it is now 3pm and 4 minutes past the hour"))
Parsing dates and times uses the same formatting style.
t, err := time.Parse("2 Jan, 3:04", "28 Feb, 10:43") t.Year() // => 0
Any non-specified fields default to the zero (0) value!
Putting it all together, we have JSON unmarshaling, together with struct initialization, and date parsing.
fields := map[string]string{} err := json.Unmarshal(incomingJSON, &fields) p := Person{} p.Name = fields["name"] t, err := time.Parse("2006/01/02", fields["born"]) ... p.Born = t p.Size = fields["size"]
HTTP response bodies are not string
or []byte
, they are io.ReadCloser
.
The json
package has a json.NewDecoder
method that takes in an io.Reader
.
The Decoder
has a Decode
method that works very much like Unmarshal
.
dec := json.NewDecoder(req.Body) if err := dec.Decode(&person); err != nil { return fmt.Errorf("decode person: %v", err) }